在TensorFlow中实现CNN进行文本分类
Implementing a CNN for Text Classification in TensorFlow
原文链接:http://www.wildml.com/2015/12/implementing-a-cnn-for-text-classification-in-tensorflow/
github代码:https://github.com/dennybritz/cnn-text-classification-tf
Implementing a CNN for Text Classification in TensorFlow
在这篇文章中,我们将实现一个类似于Kim Yoon的用于句子分类的卷积神经网络的模型。本文提出的模型在一系列文本分类任务(如情感分析)中实现了良好的分类性能,并已成为新文本分类体系结构的标准基线。
我假设您已经熟悉应用于NLP的卷积神经网络的基础知识。如果没有,我建议首先阅读了解NLP的卷积神经网络,以获得必要的背景知识。
Data and Preprocessing
我们将在这篇文章中使用的数据集是来自烂番茄的电影评论数据 - 原始论文中也使用的数据集之一。 数据集包含10,662个示例评论句子,一半正例和一半负例。 数据集的大小约为20k。 请注意,由于此数据集非常小,我们可能会过度使用强大的模型。 此外,数据集没有官方训练集和测试集的拆分,因此我们只使用10%的数据作为开发集。 原始论文报告了对数据进行10倍交叉验证的结果。
我不会在这篇文章中讨论数据预处理代码,但它可以在Github上获得并执行以下操作:
- 加载数据:从原始数据集中加载正例和负例的句子
- 数据清洗使用源论文的代码清洗文本数据
- 数据填充将每个句子填充到最大句子长度59.我们将特殊的
标记附加到所有其他句子,使它们成为59个单词。 将句子填充到相同长度是很有必要的,因为它允许我们有效地批量处理我们的数据,因为批处理中的每个示例必须具有相同的长度。 - 构建句子向量构建词汇索引并将每个单词映射到0到18,765之间的整数(词汇量大小)。 使每个句子都成为一个整数的向量。
The Model
网络的模型如下:
第一层将单词嵌入到低维向量中。下一层使用多个滤波器大小对嵌入的字向量执行卷积。例如,一次滑动3个,4个或5个字。接下来,我们将卷积层的结果最大化为长特征向量,添加dropout正则化,并使用softmax层对结果进行分类。
因为这是一篇教程性质的文章,所以我决定简化原始论文的模型:
- 我们不会使用word2vec预先训练好的词向量,相反,我们会从头开始学习如果训练词向量。
- 我们不会对权重向量强制执行L2范数约束。 对句子分类的卷积神经网络的敏感性分析发现,约束对最终结果影响不大。
- 原始论文用两个输入数据通道进行实验 - 静态和非静态词向量。 我们只使用一个通道。
向代码中添加上述扩展内容还是挺简单的,大概需要十几行代码完成
Let’s get started!
Implementation
为了允许各种超参数配置,我们将代码放入一个TextCNN类中,在init函数中生成模型图。
1 | import tensorflow as tf |
为了实例化该类,我们传递以下参数:
- sequence_length - 我们句子的长度。请记住,我们将所有句子填充为相同的长度(我们的数据集为59)。
- num_classes - 输出层中的类数,在我们的数据中为两个(正例和负例)。
- vocab_size - 我们词汇量的大小。这需要定义词向量的大小,嵌入层的shape为[vocabulary_size, embedding_size]。
- embedding_size - 词向量的维度。
- filter_sizes - 卷积过滤器覆盖的单词数。
- num_filters - 每个卷积过滤器的数量。
Input Placeholders
我们首先定义传递给网络的输入数据:
1 | # Placeholders for input, output and dropout |
tf.placeholder创建一个占位符变量,当我们在训练或测试时执行它时,我们将其提供给网络。第二个参数是输入向量的形状。None意味着该维度的长度可以是任意值。在我们的例子中,第一个维度是批量大小,并且使用None允许网络处理任意大小的批次。
在dropout层中保留神经元的概率也是网络的输入,因为我们仅在训练期间启用dropout。我们在评估模型时禁用它(稍后会详细介绍)。
Embedding Layer
我们定义的第一层是embedding 层,它将词汇词索引映射到低维矢量表示。它本质上是一个从数据中学习的查找表。
1 | with tf.device('/cpu:0'), tf.name_scope("embedding"): |
我们在这里使用了几个新功能,让我们来看看它们:
- tf.device(“/cpu:0”)强制在CPU上执行操作。默认情况下,如果有可用的话,TensorFlow会尝试将操作放在GPU上,但embedding实现目前没有GPU支持,如果置于GPU上则会引发错误。
- tf.name_scope创建一个名为“embedding” 的新名称范围。范围将所有操作添加到名为“嵌入”的顶级节点中,以便在TensorBoard中可视化网络时获得良好的层次结构。
W是我们在训练期间学习的嵌入矩阵。我们使用随机均匀分布对其进行初始化。tf.nn.embedding_lookup创建实际的embedding操作。embedding操作的结果是三维张量[None, sequence_length, embedding_size]。
TensorFlow的卷积转换操作需要一个4维张量,其尺寸对应于batch,width,height和channel。我们嵌入的结果不包含通道尺寸,因此我们手动添加它,最后的shape为[None, sequence_length, embedding_size, 1]。
Convolution and Max-Pooling Layers
现在我们已经准备好构建我们的卷积层,然后是max-pooling。请记住,我们使用不同大小的过滤器。因为每个卷积产生不同形状的张量,我们需要迭代它们,为它们中的每一个创建一个层,然后将结果合并为一个大的特征向量。
1 | pooled_outputs = [] |
这里W是我们的滤波器矩阵,h是将非线性应用于卷积输出的结果。每个过滤器都滑过整个embedding,但它覆盖的单词数量会有所不同。”VALID”padding意味着我们将过滤器滑过句子而不填充边缘,执行一个窄卷积,输出shape指定为[1, sequence_length - filter_size + 1, 1, 1]。在特定过滤器尺寸的输出上执行最大池化使我们具有张量形状[batch_size, 1, 1, num_filters]。这本质上是一个特征向量,其中最后一个维度对应于我们的特征。一旦我们从每个滤波器大小获得所有合并的输出张量,我们将它们组合成一个长形状的特征向量[batch_size, num_filters_total]。使用-1in tf.reshape告诉TensorFlow尽可能展平尺寸。
Dropout Layer
dropout可能是最常用的规则化卷积神经网络的方法。dropout背后的想法很简单。dropout层随机“禁用”其神经元的一部分。这可以防止神经元共同适应并迫使它们学习各自有用的特征。我们保持启用的神经元部分由dropout_keep_prob网络的输入定义。我们在训练期间将其设置为0.5,在评估期间设置为1(禁用dropout)。
1 | # Add dropout |
Scores and Predictions
使用max-pooling中的特征向量(应用了dropout),我们可以通过矩阵乘法和选择具有最高分数的类来生成预测。我们还可以应用softmax函数将原始分数转换为标准化概率,但这不会改变我们的最终预测。
1 | with tf.name_scope("output"): |
这里tf.nn.xw_plus_b执行Wx + b
矩阵乘法。
Loss and Accuracy
使用我们的Scores,我们可以定义损失函数。损失是衡量我们网络错误的指标,我们的目标是最小化它。分类的标准损失函数问题是交叉熵损失。
1 | # Calculate mean cross-entropy loss |
这里,tf.nn.softmax_cross_entropy_with_logits是一个函数,它根据我们的Scores和正确的输入标签计算每个类的交叉熵损失。然后我们采取损失的平均值。我们也可以使用总和,但这使得比较不同批量大小和训练/开发数据的损失变得更加困难。
我们还定义了精度的表达式,这是在训练和测试期间跟踪的有用数量。
1 | # Calculate Accuracy |
Training Procedure
之前我们定义为我们的网络训练过程中,我们需要了解TensorFlow如何使用一些基本的Sessions和Graphs。如果您已经熟悉这些概念,请随意跳过本节。
在TensorFlow中,a Session是您正在执行图形操作的环境,它包含有关变量和队列的状态。每个会话都在一个图表上运行。如果在创建变量和操作时未显式使用会话,则使用TensorFlow创建的当前默认会话。您可以通过执行session.as_default()块内的命令来更改默认会话(请参阅下文)。
A Graph包含操作和张量。您可以在程序中使用多个图形,但大多数程序只需要一个图形。您可以在多个会话中使用相同的图形,但不能在一个会话中使用多个图形。TensorFlow始终创建默认图形,但您也可以手动创建图形并将其设置为新默认图形,如下所示。显式创建会话和图表可确保在不再需要资源时正确释放资源。
1 | with tf.Graph().as_default(): |
allow_soft_placement设置允许TensorFlow回落的设备上时,优选的设备不存在实现的某些操作。例如,如果我们的代码在GPU上进行操作,并且我们在没有GPU的机器上运行代码,则不使用allow_soft_placement会导致错误。如果设置了log_device_placement,TensorFlow会记录它放置操作的设备(CPU或GPU)。这对调试很有用。FLAGS是我们程序的命令行参数。
Instantiating the CNN and minimizing the loss
当我们实例化我们的TextCNN模型时,所有定义的变量和操作将被放入我们上面创建的默认Graphs和Sessions中。
1 | cnn = TextCNN( |
接下来,我们定义如何优化网络的损耗函数。TensorFlow有几个内置的优化器。我们正在使用的是Adam优化器。
1 | global_step = tf.Variable(0, name="global_step", trainable=False) |
这里,train_op这是一个新创建的操作,我们可以运行它来对我们的参数执行渐变更新。每次执行train_op都是一个训练步骤。TensorFlow自动确定哪些变量是“可训练的”并计算其梯度。通过定义global_step变量并将其传递给优化器,我们允许TensorFlow为我们处理训练步骤的计数。每次执行时,train_op全局步骤将自动递增1 。
Summaries
TensorFlow具有Summaries概念,允许您在培训和评估期间跟踪和可视化各种变量。例如,您可能希望跟踪损失和准确度随时间的变化情况。您还可以跟踪更复杂的数量,例如图层激活的直方图。摘要是序列化对象,它们使用SummaryWriter写入磁盘。
1 | # Output directory for models and summaries |
在这里,我们分别跟踪训练和评估的Summaries。tf.merge_summary是一个便捷函数,它将多个汇总操作合并为一个我们可以执行的操作。
Checkpointing
您通常要使用的另一个TensorFlow功能是Checkpointing - 保存模型的参数以便以后恢复它们。Checkpointing可用于稍后继续训练,或使用提前停止选择最佳参数设置。使用Saver对象创建检查点。
1 | # Checkpointing |
Initializing the variables
在我们训练模型之前,我们还需要在图中初始化变量。
1 | sess.run(tf.initialize_all_variables()) |
该initialize_all_variables功能是一个方便的功能,运行所有我们为我们的变量定义的初始化的。您也可以手动调用变量的初始值设定项。如果您想要使用预先训练的值初始化嵌入,这非常有用。
Defining a single training step
现在让我们为单个训练步骤定义一个函数,在一批数据上评估模型并更新模型参数。
1 | def train_step(x_batch, y_batch): |
feed_dict包含我们传递给网络的占位符节点的数据。您必须为所有占位符节点提供值,否则TensorFlow将引发错误。处理输入数据的另一种方法是使用队列,但这超出了本文的范围。
接下来,我们执行train_opusing session.run,它返回我们要求它评估的所有操作的值。请注意,train_op什么都不返回,它只是更新我们网络的参数。最后,我们打印当前培训批次的丢失和准确性,并将摘要保存到磁盘。请注意,如果批次较小,批次培训批次的损失和准确性可能会有很大差异。由于我们使用的是辍学,因此您的培训指标可能会比评估指标更差。
我们编写了一个类似的函数来评估任意数据集的损失和准确性,例如验证集或整个训练集。基本上这个功能与上面的功能相同,但没有训练操作。它还会禁用丢失。
1 | def dev_step(x_batch, y_batch, writer=None): |
Training loop
最后,我们准备编写训练循环了。我们迭代批量数据,train_step为每个批次调用函数,不定时评估和检查我们的模型:
1 | # Generate batches |
这里,batch_iter是我编写的批处理数据的辅助函数,tf.train.global_step是返回值的便捷函数global_step。
此处提供完整的训练代码